#include <nds.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include "hashes.h"
#include "arrays.h"
#include "poker_eval.h"
#include "screen.h"

static char *value_str[] = {
    (char *)"",
    (char *)"Straight Flush",
    (char *)"Quad",
    (char *)"Full House",
    (char *)"Flush ",
    (char *)"Straight",
    (char *)"Trip",
    (char *)"Two Pair",
    (char *)"Pair of",
    (char *)"High Card"
};

char SUIT_HEART[] =   {158,159,'\0'};
char SUIT_DIAMOND[] = {160,161,'\0'};
char SUIT_SPADE[] =   {162,163,'\0'};
char SUIT_CLUB[] =    {164,165,'\0'};
char RANKS[] =        {'2','3','4','5','6','7','8','9',189,'J','Q','K','A','e','\0'}; // "23456789TJQKA"

char SUIT_HEART_SMALL[] =   {28,'\0'};
char SUIT_DIAMOND_SMALL[] = {29,'\0'};
char SUIT_SPADE_SMALL[] =   {30,'\0'};
char SUIT_CLUB_SMALL[] =    {31,'\0'};
char RANKS_SMALL[] =        {15,16,17,18,19,20,21,22,23,24,25,26,27,'e','\0'}; // "23456789TJQKA"

extern int NumCards;

// Looking for EXACTLY numMatchingCards that match.
// 'ignore' parameter means we don't include that rank in the match - used for 2nd pass on 2 pair and full house.
int WhichNOfAKind(int numMatchingCards, int *hand, int ignore = -1)
{
    int f;
    int rank;
    int lastRank = 13;
    int matches = 0;

    for(f = 0 ; f < 5 ; f++)
    {
        rank = (hand[f] >> 8) & 0xF;
        if(rank == lastRank)
        {
            if(ignore == -1 || rank != ignore)
            {
                matches++;
            }
        }
        else
        {
            matches = 0;
            lastRank = rank;
            if(ignore == -1 || rank != ignore)
            {
                matches = 1;
            }
        }

        if(matches == numMatchingCards)
        {
            return rank;
        }
    }
    return 13;
}

void SortHand(int *hand)
{
    int f, g, highPos, rank, highRank;
    int newHand[5];

    for(f = 0 ; f < 5 ; f++)
    {
        highPos = -1;
        highRank = -1;

        for(g = 0 ; g < 5 ; g++)
        {
            if(hand[g] == -1)
            {
                continue;
            }

            rank = (hand[g] >> 8) & 0xF;
            if(highPos == -1 || rank > highRank)
            {
                highPos = g;
                highRank = rank;
            }
        }
        newHand[f] = hand[highPos];
        hand[highPos] = -1;
    }
    memcpy(hand, newHand, 5 * sizeof(int));
}

char *GetHandInfo(int type, int *hand, int *holeCards)
{
    static char handInfo[40];
    int rank1;
    int rank2;
    bool set = false;

    if(NumCards == 2)
    {
        rank1 = (holeCards[0] >> 8) & 0xF;
        rank2 = (holeCards[1] >> 8) & 0xF;
    }
    else
    {
        rank1 = (hand[0] >> 8) & 0xF;
        rank2 = (hand[1] >> 8) & 0xF;
    }
    if(rank1 == rank2)
    {
        set = true;
    }

    SortHand(hand);

    switch(type)
    {
    case 1:
        // Straight flush - what else do you need to know?
        sprintf(handInfo, "%s", value_str[type]);
        break;

    case 2:
        // Quads
        rank1 = WhichNOfAKind(4, hand);
        sprintf(handInfo, "%s %cs", value_str[type], RANKS[rank1]);
        break;

    case 3:
        // Full House
        rank1 = WhichNOfAKind(3, hand);
        rank2 = WhichNOfAKind(2, hand, rank1);
        sprintf(handInfo, "%s %c,%c", value_str[type], RANKS[rank1], RANKS[rank2]);
        break;

    case 4:
        {
            // Flush
            int f;

            strcpy(handInfo, value_str[type]);
            for(f = 0 ; f < 5 ; f++)
            {
                rank1 = (hand[f] >> 8) & 0xF;
                handInfo[6 + 4 - f] = RANKS[rank1];
            }
            handInfo[11] = '\0';
        }
        break;

    case 5:
        // Straight
        rank1 = (hand[4] >> 8) & 0xF;
        rank2 = (hand[0] >> 8) & 0xF;

        if(rank1 == 0 && rank2 == 12)
        {
            // Special case A-5 straight, would be displayed as 2-A.  Fix the ranks for display.
            rank1 = 12;
            rank2 = 3;
        }
        sprintf(handInfo, "%s %c-%c", value_str[type], RANKS[rank1], RANKS[rank2]);
        break;

    case 6:
        {
            // Three of a kind
            rank1 = WhichNOfAKind(3, hand);
            sprintf(handInfo, "%s %cs", set ? "Set of" : value_str[type], RANKS[rank1]);
        }
        break;

    case 7:
        // Two pair
        rank1 = WhichNOfAKind(2, hand);
        rank2 = WhichNOfAKind(2, hand, rank1);
        sprintf(handInfo, "%s %c,%c", value_str[type], RANKS[rank1], RANKS[rank2]);
        break;

    case 8:
        // Pair
        rank1 = WhichNOfAKind(2, hand);
        sprintf(handInfo, "%s %cs", value_str[type], RANKS[rank1]);
        break;

    case 9:
        // High card
        rank1 = (hand[0] >> 8) & 0xF;
        sprintf(handInfo, "%s %c", value_str[type], RANKS[rank1]);
        break;
    }

    return handInfo;
}

int TestHand(int *board, int *holecards, char *hand_value, bool omaha)
{
    int hand[9], bestHand[5], score;

    memcpy(hand, board, 5 * sizeof(int));
    memcpy(&hand[5], holecards, 4 * sizeof(int));

    if(omaha)
    {
        score = eval_omaha(hand, bestHand );
    }
    else
    {
        score = eval_7hand(hand, bestHand);
    }

    sprintf(hand_value, "%s", GetHandInfo(hand_rank(score), bestHand, holecards));

    return score;
}

void Randomize(void)
{
    srand(time(NULL));
}

void shuffle_deck( int *deck )
{
    int i, n, temp[52];

    for ( i = 0; i < 52; i++ )
        temp[i] = deck[i];

    for ( i = 0; i < 52; i++ )
    {
        do {
            n = rand() % 52;
        } while ( temp[n] == 0 );
        deck[i] = temp[n];
        temp[n] = 0;
    }
}

// Paul D. Senzee's Optimized Hand Evaluator
//   for Cactus Kev's Poker Hand Evaluator
//
// (c) Paul D. Senzee.
// Portions (in eval_5hand_fast) (c) Kevin L. Suffecool.
// psenzee@yahoo.com
//
// Senzee 5
// http://senzee.blogspot.com

extern short flushes[];
extern short unique5[];

unsigned find_fast(unsigned u)
{
    unsigned a, b, r;
    u += 0xe91aaa35;
    u ^= u >> 16;
    u += u << 8;
    u ^= u >> 4;
    b  = (u >> 8) & 0x1ff;
    a  = (u + (u << 2)) >> 19;
    r  = a ^ hash_adjust[b];
    return r;
}

//
//   This routine initializes the deck.  A deck of cards is
//   simply an integer array of length 52 (no jokers).  This
//   array is populated with each card, using the following
//   scheme:
//
//   An integer is made up of four bytes.  The high-order
//   bytes are used to hold the rank bit pattern, whereas
//   the low-order bytes hold the suit/rank/prime value
//   of the card.
//
//   +--------+--------+--------+--------+
//   |xxxbbbbb|bbbbbbbb|cdhsrrrr|xxpppppp|
//   +--------+--------+--------+--------+
//
//   p = prime number of rank (deuce=2,trey=3,four=5,five=7,...,ace=41)
//   r = rank of card (deuce=0,trey=1,four=2,five=3,...,ace=12)
//   cdhs = suit of card
//   b = bit turned on depending on rank of card
//
// JMD: added "void" return type
void init_deck( int *deck )
{
    int i, j, n = 0, suit = 0x8000;

    for ( i = 0; i < 4; i++, suit >>= 1 )
        for ( j = 0; j < 13; j++, n++ )
            deck[n] = primes[j] | (j << 8) | suit | (1 << (16+j));
}


//  This routine will search a deck for a specific card
//  (specified by rank/suit), and return the INDEX giving
//  the position of the found card.  If it is not found,
//  then it returns -1
//
int find_card( int rank, int suit, int *deck )
{
    int i, c;

    for ( i = 0; i < 52; i++ )
    {
        c = deck[i];
        if ( (c & suit)  &&  (RANK(c) == rank) )
            return( i );
    }
    return( -1 );
}

int hand_rank( short val )
{
    if (val > 6185) return(HIGH_CARD);        // 1277 high card
    if (val > 3325) return(ONE_PAIR);         // 2860 one pair
    if (val > 2467) return(TWO_PAIR);         //  858 two pair
    if (val > 1609) return(THREE_OF_A_KIND);  //  858 three-kind
    if (val > 1599) return(STRAIGHT);         //   10 straights
    if (val > 322)  return(FLUSH);            // 1277 flushes
    if (val > 166)  return(FULL_HOUSE);       //  156 full house
    if (val > 10)   return(FOUR_OF_A_KIND);   //  156 four-kind
    return(STRAIGHT_FLUSH);                   //   10 straight-flushes
}

int eval_5hand_fast(int c1, int c2, int c3, int c4, int c5)
{
    int q = (c1 | c2 | c3 | c4 | c5) >> 16;
    short s;
    if (c1 & c2 & c3 & c4 & c5 & 0xf000) return flushes[q]; // check for flushes and straight flushes
    if ((s = unique5[q]))                return s;          // check for straights and high card hands
    return hash_values[find_fast((c1 & 0xff) * (c2 & 0xff) * (c3 & 0xff) * (c4 & 0xff) * (c5 & 0xff))];
}

short eval_5hand( int *hand )
{
    int c1, c2, c3, c4, c5;

    c1 = *hand++;
    c2 = *hand++;
    c3 = *hand++;
    c4 = *hand++;
    c5 = *hand;

    return( eval_5hand_fast(c1,c2,c3,c4,c5) );
}

// This is a non-optimized method of determining the
// best five-card hand possible out of seven cards.
// I am working on a faster algorithm.
//
short eval_7hand( int *hand, int *bestHand )
{
    int i, j, q, best = 9999, subhand[5];

    for ( i = 0; i < 21; i++ )
    {
        for ( j = 0; j < 5; j++ )
            subhand[j] = hand[ perm7[i][j] ];
        q = eval_5hand( subhand );
        if ( q < best )
        {
            best = q;
            memcpy(bestHand, subhand, sizeof(int) * 5);
        }
    }
    return( best );
}

// Omaha - 60 permutations instead of 21.
//
short eval_omaha(int *hand, int *bestHand)
{
    int i, j, q, best = 9999, subhand[5];

    for ( i = 0; i < 60; i++ )
    {
        for ( j = 0; j < 5; j++ )
            subhand[j] = hand[ omahaPermutations[i][j] ];
        q = eval_5hand( subhand );
        if ( q < best )
        {
            best = q;
            memcpy(bestHand, subhand, sizeof(int) * 5);
        }
    }
    return( best );
}

void ConvertHand( int *hand, int n, char *handStr )
{
    if(n)
    {
        int i, r;
        char *suit;

        char temp[12];
        handStr[0] = '\0';

        for ( i = 0; i < n; i++, hand++ )
        {
            r = (*hand >> 8) & 0xF;
            if ( *hand & 0x8000 )
                suit = (char *)SUIT_CLUB;
            else if ( *hand & 0x4000 )
                suit = (char *)SUIT_DIAMOND;
            else if ( *hand & 0x2000 )
                suit = (char *)SUIT_HEART;
            else
                suit = (char *)SUIT_SPADE;

            sprintf(temp,"%c%s ", RANKS[r], suit );
            strcat(handStr,temp);
        }
        handStr[strlen(handStr) - 1] = '\0';
    }
    else
    {
        handStr[0] = '\0';
    }
}

void ConvertHandSmall( int *hand, int n, char *handStr )
{
    if(n)
    {
        int i, r;
        char *suit;

        char temp[12];
        handStr[0] = '\0';

        for ( i = 0; i < n; i++, hand++ )
        {
            r = (*hand >> 8) & 0xF;
            if ( *hand & 0x8000 )
                suit = (char *)SUIT_CLUB_SMALL;
            else if ( *hand & 0x4000 )
                suit = (char *)SUIT_DIAMOND_SMALL;
            else if ( *hand & 0x2000 )
                suit = (char *)SUIT_HEART_SMALL;
            else
                suit = (char *)SUIT_SPADE_SMALL;

            sprintf(temp,"%c%s ", RANKS_SMALL[r], suit );
            strcat(handStr,temp);
        }
        handStr[strlen(handStr) - 1] = '\0';
    }
    else
    {
        handStr[0] = '\0';
    }
}
